Skip to content

feat(otel-collector): add statsdreceiver for StatsD/DogStatsD ingestion - #3100

Merged
kodiakhq[bot] merged 16 commits into
ClickHouse:mainfrom
arj22:feat/add-statsd-receiver
Sep 22, 2026
Merged

kodiakhq[bot] merged 16 commits into
ClickHouse:mainfrom
arj22:feat/add-statsd-receiver

Conversation

@arj22

@arj22 arj22 commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds the OpenTelemetry statsdreceiver to HyperDX's custom collector build, so users can ingest StatsD metrics directly without a separate bridge. Purely additive/opt-in — no default pipeline or behavior changes.

The docs call out one real limitation from upstream: this receiver keeps aggregation state per-instance with no cross-replica coordination, so it shouldn't be wired into HyperDX's multi-replica otel-collector Deployment — it needs a dedicated single-replica instance.

Test plan

  • Built the collector and confirmed statsdreceiver wasn't previously compiled in, then confirmed it's present after the change (otelcontribcol components).
  • Validated the documented config against the compiled binary and actually started the collector with it (reached "Everything is ready"), not just validate.

🤖 Generated with Claude Code

Compiles in `statsdreceiver` so StatsD and DogStatsD-formatted metrics
can be ingested directly, without a separate StatsD-to-OTLP bridge.
Documents that it doesn't support horizontally-scaled deployments (a
real, upstream-documented limitation, not HyperDX-specific) and that it
should not be wired into the OpAMP-managed otel-collector Deployment,
which runs multiple replicas for OTLP ingest availability.

Verified against the real compiled binary (pinned v0.155.0): confirmed
`statsdreceiver` is not currently compiled in, added it, rebuilt, and
confirmed the documented config both passes `otelcontribcol validate
--config` and actually starts (`otelcontribcol --config ...`, reaching
"Everything is ready. Begin running and processing data."), using the
same multi-config merge the container's own entrypoint uses.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Sep 9, 2026

Copy link
Copy Markdown

@arj22 is attempting to deploy a commit to the HyperDX Team on Vercel.

A member of the Team first needs to authorize it.

@changeset-bot

changeset-bot Bot commented Sep 9, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 2712e01

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@hyperdx/otel-collector Minor
@hyperdx/api Minor
@hyperdx/app Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@greptile-apps

greptile-apps Bot commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 5/5

The PR appears safe to merge; no blocking or non-blocking issues remain in the reviewed changes.

Summary

Adds the OpenTelemetry StatsD receiver to the custom collector build for opt-in StatsD and DogStatsD ingestion.

  • Documents standalone and OpAMP pipeline configurations, DogStatsD bare-tag handling, and the receiver's single-replica constraint.
  • Adds opt-in UDP 8125 mappings to the Compose examples without reserving the host port by default.
  • Exposes UDP 8125 as container image metadata.
  • Changes since the previous review only merge newer main changes and do not modify this PR's seven-file changeset.
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart LR
    C[StatsD or DogStatsD client] -->|UDP 8125| R[statsd receiver]
    R --> P[Dedicated metrics/statsd pipeline]
    P --> B[memory_limiter and batch]
    B --> E[ClickHouse exporter]
Loading

Reviews (15) · Last reviewed commit: "Merge branch 'main' into feat/add-statsd..."

@github-actions

github-actions Bot commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor

Deep Review

Scope: PR #3100 — feat(otel-collector): add statsdreceiver (base 6db385cd). Config + docs only; 7 files, no executable code.
Intent: Compile the OpenTelemetry statsdreceiver into HyperDX's custom collector so StatsD/DogStatsD can be ingested opt-in, with no default pipeline or behavior change.
Mode: report-only (read-only; no edits, commits, or artifacts).

Verified directly against the checkout: no default config (docker/otel-collector/config*.yaml) references statsd, so compiling the receiver in is inert until a user wires it into a pipeline; EXPOSE 8125/udp is image metadata and does not bind a host port; both 8125/udp host mappings ship commented out; and the .changeset/config.json fixed group intentionally cascades the minor bump to api/app (expected, not a defect).

✅ No critical issues found. This is a low-risk, additive, opt-in change; the compose/Dockerfile/README changes are internally consistent and prior review feedback on port publication, DogStatsD enable_simple_tags, and the OpAMP dedicated-pipeline gap all appear addressed in the current diff.

🟡 P2 — recommended

  • packages/otel-collector/builder-config.yaml:54 — No automated check asserts statsdreceiver stays compiled into the collector or that the documented sample config starts, so a future collector/builder bump could silently drop this opt-in feature; the receiver and its configs were validated manually only.
    • Fix: Add a component-presence assertion (and a config-startup check) for statsd alongside the existing smoke-tests/otel-collector/*.bats harness, or track it as an explicit follow-up.
    • testing, previous-comments
🔵 P3 nitpicks (1)
  • packages/otel-collector/README.md:405 — The closing "Both shapes are verified against the real compiled binary…" paragraph is test-rigor meta-commentary aimed at reviewers rather than user-facing guidance.
    • Fix: Trim the verification note from the user docs or condense it to a single line.

Reviewers (6): correctness, testing, maintainability, project-standards, reliability, previous-comments.

Testing gaps:

  • No regression coverage for the new receiver (see P2); the author deferred this to a follow-up, citing the spanmetricsconnector precedent in Add spanmetricsconnector for RED metrics from Datadog traces #3044.
  • A maintainer reported the PR's CI smoke tests failing (2026-09-18) and unresolved as of the latest comments; the diff itself contains no code path that would cause a failure, so confirm CI is green before merge rather than treating it as a code defect here.

No finding blocks merge automatically. A maintainer will expect P0/P1 findings in code this PR changes to be fixed; P2/P3 are your call -- fix or reply. Never fix findings about surrounding code here; reply instead. Do not widen the PR. How to respond

Comment thread packages/otel-collector/README.md Resolved
Comment thread packages/otel-collector/README.md Resolved
@github-actions

github-actions Bot commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor

PR Review

If you are a coding agent acting for the author, read this first.
Nothing below blocks merge automatically; a maintainer decides. What they will expect:

  • 🔴 critical or 🟠 major in code this PR adds or changes: fix before asking for review.
  • 🔵 minor in code this PR changes: your call. Fix if small, otherwise reply.
  • Anything about surrounding code, or asking you to widen the change (hoist a helper,
    dedupe with another file, fix other call sites): never fix here, whatever the
    severity. Reply with one sentence; if it is critical, say so plainly so a human sees it.

One commit per review round. After two rounds, stop and ask a maintainer to review
scope rather than addressing more automated findings. Full rule: AGENTS.md.

3 finding(s): 🔴 0 critical · 🟠 0 major · 🔵 3 minor

3 posted as inline comment(s) on the changed lines.


Severity is the reviewer's own estimate and is used for ordering, not filtering. No finding blocks merge automatically; a maintainer decides.

@pulpdrew
pulpdrew requested a review from wrn14897 September 9, 2026 16:41
…laim

- Removed the timer_histogram_mapping example claiming to route
  "timer/histogram-type" metrics to an exponential histogram. Verified
  against the pinned v0.155.0 source (factory.go's
  defaultTimerHistogramMapping): timer, histogram, AND distribution all
  default to a gauge observer, and the example only remapped `timing`
  - histogram/distribution metrics would still land as gauges,
  contradicting the comment.
- Reframed the section around plain StatsD rather than DogStatsD
  specifically - the receiver handles any StatsD client; DogStatsD's
  tag extension is supported but not required, and centering the docs
  on one vendor's dialect was misleading and pulled in the now-removed,
  Datadog-distribution-specific histogram-mapping example.
- Net simplification: fewer lines, no vendor-specific framing, no
  incorrect claim.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Comment thread packages/otel-collector/README.md Resolved
Comment thread packages/otel-collector/README.md Resolved
Comment thread packages/otel-collector/README.md Resolved
Comment thread packages/otel-collector/README.md Resolved
Comment thread packages/otel-collector/README.md Resolved
…e_simple_tags

- Adding statsd to the default `metrics` pipeline via
  CUSTOM_OTELCOL_CONFIG_FILE silently no-ops in OpAMP supervisor mode
  (HyperDX's default deployment): opampController.ts overwrites that
  pipeline's receivers/exporters at runtime the same way it does for
  spanmetricsconnector (see the span metrics section above, ClickHouse#2351).
  Added a second example using a dedicated `metrics/statsd` pipeline
  name, which the remote config never touches, mirroring the existing
  OpAMP-mode span_metrics example. Verified against the real compiled
  binary by reconstructing opampController's generated base config as a
  static fixture and running `validate --config` against the merge.
- Corrected the multi-replica warning: it was written as if OpAMP mode
  itself implies multiple replicas, but HyperDX's shipped default
  deployments run the OpAMP-managed collector as a single instance —
  the actual constraint is scaling *whichever* collector ingests statsd
  beyond one replica, independent of which mode it runs in.
- Restored `enable_simple_tags: true` to both examples and qualified
  the intro claim about DogStatsD tag support: valued tags work with no
  extra config, but bare tags (`#mytag`) are silently dropped without
  this option.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Comment thread packages/otel-collector/README.md Resolved
Comment thread packages/otel-collector/README.md Resolved
Comment thread packages/otel-collector/README.md Resolved
Comment thread packages/otel-collector/README.md Resolved
arj22 and others added 2 commits September 9, 2026 22:15
StatsD defaults to UDP, and neither the shipped compose files nor the
collector/all-in-one Dockerfiles published or exposed 8125 - a user who
added the documented statsd config verbatim on top of the default stack
got no data and no error (UDP sends are fire-and-forget, so nothing logs
the drop). Publish `8125:8125/udp` on the otel-collector service in
docker-compose.yml and docker-compose.dev.yml (parameterized via
HDX_DEV_OTEL_STATSD_PORT, matching the other dev port overrides), and
add 8125/udp to the EXPOSE lines in docker/otel-collector/Dockerfile
(both dev and prod stages) and docker/hyperdx/Dockerfile (all-in-one).

statsd itself stays opt-in (nothing listens on the port until a pipeline
config adds the receiver), so this is a no-op for anyone not using it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Comment thread docker-compose.yml Resolved
Comment thread docker-compose.dev.yml Resolved
Comment thread docker-compose.yml Resolved
Comment thread packages/otel-collector/README.md Resolved
Comment thread packages/otel-collector/README.md Resolved
An unconditional fixed host port mapping fails the whole otel-collector
service's startup if anything else already owns 8125/udp (a local StatsD
daemon or Datadog Agent commonly does) - since statsd itself is opt-in
and off by default, this would have broken the default stack for anyone
not using the feature, just for having it in their environment.

Commented the mapping out by default in both compose files, matching the
existing opt-in-port convention already used in the same files (db's
27017, ch-server's 8123/9000). Uncommenting it remains a one-line change,
documented in the README section.
Comment thread packages/otel-collector/README.md Resolved
Comment thread packages/otel-collector/README.md Resolved
Comment thread packages/otel-collector/README.md Resolved
Comment thread docker-compose.dev.yml Resolved
@arj22

arj22 commented Sep 10, 2026

Copy link
Copy Markdown
Contributor Author

@wrn14897 this is ready for review.

@wrn14897 wrn14897 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Comment thread packages/otel-collector/README.md Resolved
Comment thread packages/otel-collector/README.md Resolved
Comment thread docker-compose.dev.yml Resolved
Comment thread docker-compose.yml Resolved
Comment thread packages/otel-collector/README.md Resolved
@arj22

arj22 commented Sep 18, 2026

Copy link
Copy Markdown
Contributor Author

@wrn14897 @pulpdrew can one of you merge this?

Comment thread docker-compose.dev.yml
# packages/otel-collector/README.md). Left commented out by default:
# a fixed host port mapping fails the whole service's startup if
# something else on the host already owns 8125/udp.
# - '${HDX_DEV_OTEL_STATSD_PORT:-8125}:8125/udp' # statsd receiver

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 minor — HDX_DEV_OTEL_STATSD_PORT is never allocated per dev slot, so it hardcodes 8125 for every worktree

Every other HDX_DEV_*_PORT in this file is assigned a per-slot value in scripts/dev-env.sh (HDX_DEV_OTEL_JSON_HTTP_PORT=$((31100 + HDX_DEV_SLOT)), line 57) and documented in the port table in agent_docs/development.md:63-67. HDX_DEV_OTEL_STATSD_PORT appears in neither, so the :-8125 fallback always wins and two worktrees that both uncomment this line collide on the host — exactly the startup failure the comment says it is avoiding. Add HDX_DEV_OTEL_STATSD_PORT=$((31200 + HDX_DEV_SLOT)) plus its export in scripts/dev-env.sh and a row in the agent_docs/development.md table.

Comment thread docker-compose.yml
- '4318:4318' # OTLP http receiver
- '8888:8888' # metrics extension
# Uncomment to enable statsd ingestion (also requires adding the
# `statsd` receiver to a pipeline via CUSTOM_OTELCOL_CONFIG_FILE - see

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 minor — docker-compose.yml's otel-collector has no CUSTOM_OTELCOL_CONFIG_FILE env var or config mount, so the instruction can't be followed

Unlike docker-compose.dev.yml (which sets CUSTOM_OTELCOL_CONFIG_FILE at line 39 and bind-mounts custom.config.yaml at line 52), this service declares no volumes: and no such env var, so a user who uncomments the port has nowhere to put the statsd receiver block. Add the matching commented-out environment:/volumes: lines here (or in the README's statsd section) so the opt-in is actually completable in the shipped production compose.

(not merges) those keys — see the [span metrics section
above](#computing-span-metrics-from-ingested-traces) for the same
behavior. Adding `statsd` to `metrics.receivers` from
`CUSTOM_OTELCOL_CONFIG_FILE` gets silently dropped: no error, the receiver

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 minor — README says an unwired statsd receiver "just starts"; an unreferenced receiver is never instantiated

When the OpAMP remote config overwrites metrics.receivers, the statsd receiver is left referenced by no pipeline, and the collector never builds or starts it — nothing binds 8125/udp at all. The equivalent span-metrics paragraph gets this right ("the connector is just compiled in and wired to nothing", line 282). Reword to match, since the current text sends a debugging user toward the port mapping instead of the pipeline.

statsd:
# Defaults to localhost:8125, which only accepts local traffic -
# 0.0.0.0 is required here since traffic arrives from outside the
# container (the published port above forwards to the container's

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 minor — The parenthetical explaining why 0.0.0.0 is needed states the mechanism backwards

A published port is DNAT'd to the container's bridge interface, not to the container's loopback — which is precisely why a receiver bound to localhost:8125 is unreachable. As written ("the published port above forwards to the container's own loopback otherwise") it implies the default localhost binding would work, contradicting the advice it supports. Reword to "traffic arrives on the container's external interface, not its loopback".

OpAMP-managed collector as a single instance, so this doesn't bite out of
the box — it only matters once *some* collector instance ingesting
`statsd` is scaled to multiple replicas, at which point that traffic
needs to go to one dedicated single-replica instance instead.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 minor — New section doesn't note that statsd has no authentication, unlike every other exposed receiver

packages/api/src/opamp/controllers/opampController.ts:361-371 and :397-406 attach bearertokenauth to otlp/hyperdx and datadog when collector auth is enforced, and the Datadog section here has a dedicated "Authentication" subsection. statsdreceiver supports no authenticator, so the recommended endpoint: 0.0.0.0:8125 plus a published 8125:8125/udp is an unauthenticated, spoofable metrics ingest path even in an auth-enforced deployment. Add a sentence saying so and recommending the port be restricted at the network layer (e.g. bind the host side to an internal address).

@pulpdrew

Copy link
Copy Markdown
Contributor

@arj22 sorry for the delay, it looks like the smoke tests are failing though. Could you take a look at that please?

Comment thread docker-compose.dev.yml Resolved
Comment thread packages/otel-collector/README.md Resolved
Comment thread packages/otel-collector/README.md Resolved
@arj22

arj22 commented Sep 19, 2026 •

Copy link
Copy Markdown
Contributor Author

@pulpdrew Looked into it — likely not caused by this PR, the diff here doesn't touch ClickHouse or the smoke-test setup. The failure was ch-server-compat (the pinned old ClickHouse image used for compat-schema testing) not becoming healthy in time while the main ch-server came up fine — probably CI resource contention. Looks like a flake, worth a rerun.

@pulpdrew

Copy link
Copy Markdown
Contributor

@arj22 Apologies, this isn't a flake, but is not related to your changes. Fixing in #3170, then we can get this PR merged.

kodiakhq Bot pushed a commit that referenced this pull request Sep 22, 2026
## Summary

Fixes the otel smoke tests, which fail because #3142 added a prometheus related ClickHouse config property that causes the (deliberately older) ClickHouse compat version used in the otel smoke tests to fail on startup. This is fixed by removing the prometheus setting from the compat container's config.

To prevent this in the future, the conditions for running the smoke tests have been widened to include changes to the ClickHouse config.

Related: Smoke tests are failing on #3100.

### Screenshots or video

No behavior changes.

### How to test on Vercel preview

No behavior changes.

### References



- Linear Issue: Closes HDX-5434
- Related PRs:
@kodiakhq
kodiakhq Bot merged commit 3ab758e into ClickHouse:main Sep 22, 2026
38 of 42 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants